package com.zq.it.cms.common;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaBuilder.In;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.hibernate.jpa.criteria.OrderImpl;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;

import com.zq.it.cms.common.util.CommUtil;

public class MapSpecification<Entity> extends HashMap<String, Object> implements Specification<Entity>{
	
	private static enum Operator{
		EQ, NEQ, START, END, CONTAINS, NSTART, NEND, NCONTAINS, LT, LTE, GT, GTE, IN, NOTIN, NULL, NOTNULL
		;
	}
	
	private Sort sort;
	
	public MapSpecification(){
		
	}
	public MapSpecification(Sort sort){
		this.sort = sort;
	}
	public MapSpecification(Map<String, Object> params, Sort sort) {
		super(params);
		this.sort = sort;
	}
	
	public MapSpecification<Entity> build(Map<String, String> params){
		clear();
		for(String key : params.keySet()){
			if(Pattern.matches("^[\\w\\.]+:[\\w\\.]+:[\\w\\.]+$", key)){
				String className = key.substring(key.lastIndexOf(":") + 1);
				try {
					Class<?> claz = Class.forName(className);
					if(claz == int.class || claz == Integer.class){
						put(key, CommUtil.toInt(params.get(key)));
					}else if(claz == long.class || claz == Long.class){
						put(key, CommUtil.toLong(params.get(key)));
					}else if(claz == float.class || claz == Float.class){
						put(key, CommUtil.toFloat(params.get(key)));
					}else if(claz == double.class || claz == Double.class){
						put(key, CommUtil.toDouble(params.get(key)));
					}else if(claz == BigDecimal.class){
						put(key, CommUtil.toBigDecimal(params.get(key)));
					}else if(claz == Date.class){
						put(key, CommUtil.parseDate(params.get(key)));
					}else{
						put(key, params.get(key));
					}
				} catch (Exception e) {
					throw new RuntimeException(e.getMessage());
				}
			}else{
				put(key, params.get(key));
			}
		}
		return this;
	}
	
	public Sort getSort() {
		return sort;
	}
	public void setSort(Sort sort) {
		this.sort = sort;
	}
	public MapSpecification<Entity> equal(String field, Object value){
		String key = generateKey(Operator.EQ, field, null);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> equal(String field, Object value, Class<?> claz){
		String key = generateKey(Operator.EQ, field, claz);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> notEqual(String field, Object value){
		String key = generateKey(Operator.NEQ, field, null);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> notEqual(String field, Object value, Class<?> claz){
		String key = generateKey(Operator.NEQ, field, claz);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> start(String field, String value){
		String key = generateKey(Operator.START, field, null);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> start(String field, String value, Class<?> claz){
		String key = generateKey(Operator.START, field, claz);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> end(String field, String value){
		String key = generateKey(Operator.END, field, null);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> end(String field, String value, Class<?> claz){
		String key = generateKey(Operator.END, field, claz);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> contains(String field, String value){
		String key = generateKey(Operator.CONTAINS, field, null);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> contains(String field, String value, Class<?> claz){
		String key = generateKey(Operator.CONTAINS, field, claz);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> notStart(String field, String value){
		String key = generateKey(Operator.NSTART, field, null);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> notStart(String field, String value, Class<?> claz){
		String key = generateKey(Operator.NSTART, field, claz);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> notEnd(String field, String value){
		String key = generateKey(Operator.NEND, field, null);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> notEnd(String field, String value, Class<?> claz){
		String key = generateKey(Operator.NEND, field, claz);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> notContains(String field, String value){
		String key = generateKey(Operator.NCONTAINS, field, null);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> notContains(String field, String value, Class<?> claz){
		String key = generateKey(Operator.NCONTAINS, field, claz);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> lessThan(String field, Object value){
		String key = generateKey(Operator.LT, field, null);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> lessThan(String field, Object value, Class<?> claz){
		String key = generateKey(Operator.LT, field, claz);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> lessThanEqual(String field, Object value){
		String key = generateKey(Operator.LTE, field, null);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> lessThanEqual(String field, Object value, Class<?> claz){
		String key = generateKey(Operator.LTE, field, claz);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> greaterThan(String field, Object value){
		String key = generateKey(Operator.GT, field, null);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> greaterThan(String field, Object value, Class<?> claz){
		String key = generateKey(Operator.GT, field, claz);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> greaterThanEqual(String field, Object value){
		String key = generateKey(Operator.GTE, field, null);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> greaterThanEqual(String field, Object value, Class<?> claz){
		String key = generateKey(Operator.GTE, field, claz);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> in(String field, Collection<?> value){
		String key = generateKey(Operator.IN, field, null);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> in(String field, Collection<?> value, Class<?> claz){
		String key = generateKey(Operator.IN, field, claz);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> notIn(String field, Collection<?> value){
		String key = generateKey(Operator.NOTIN, field, null);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> notIN(String field, Collection<?> value, Class<?> claz){
		String key = generateKey(Operator.NOTIN, field, claz);
		put(key, value);
		return this;
	}
	public MapSpecification<Entity> isNull(String field){
		String key = generateKey(Operator.NULL, field, null);
		put(key, null);
		return this;
	}
	public MapSpecification<Entity> notNull(String field){
		String key = generateKey(Operator.NOTNULL, field, null);
		put(key, null);
		return this;
	}
	private String generateKey(Operator operator, String field, Class<?> claz){
		return field + ":" + operator + (claz == null ? "" : ":" + claz.getName());
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private Predicate getPredicate(From<Entity, ?> root, CriteriaBuilder cb, String field, Object value, Operator operator, Class<?> claz){
		Expression<?> path = root.get(field);
		if(claz != null){
			path = path.as(claz);
		}
		switch (operator) {
		case EQ:
			return cb.equal(path, value);
		case NEQ:
			return cb.equal(path, value);
		case START:
			return cb.like(path.as(String.class), value + "%");
		case END:
			return cb.like(path.as(String.class), "%" + value);
		case CONTAINS:
			return cb.like(path.as(String.class), "%" + value + "%");
		case NSTART:
			return cb.not(cb.like(path.as(String.class), value + "%"));
		case NEND:
			return cb.not(cb.like(path.as(String.class), "%" + value));
		case NCONTAINS:
			return cb.not(cb.like(path.as(String.class), "%" + value + "%"));
		case LT:
			return cb.lessThan((Path)path, (Comparable)value);
		case LTE:
			return cb.lessThanOrEqualTo((Path)path, (Comparable)value);
		case GT:
			return cb.greaterThan((Path)path, (Comparable)value);
		case GTE:
			return cb.greaterThanOrEqualTo((Path)path, (Comparable)value);
		case IN:
			In<Object> in = cb.in(path);
			for(Object obj : (Collection<?>)value){
				in.value(obj);
			}
			return in;
		case NOTIN:
			In<Object> in1 = cb.in(path);
			for(Object obj : (Collection<?>)value){
				in1.value(obj);
			}
			return cb.not(in1);
		case NULL:
			return cb.isNull(path);
		case NOTNULL:
			return cb.isNotNull(path);
		}
		return null;
	}
	
	public Predicate toPredicate(Root<Entity> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
		Iterator<String> it = keySet().iterator();
		Map<String,From<Entity, ?>> joins = new HashMap<String, From<Entity,?>>();
		List<Predicate> predicateList = new ArrayList<Predicate>();
		while(it.hasNext()){
			String key = it.next();
			Object value = get(key);
			String opers[] = key.split(":");
			if(opers.length == 0){
				opers = new String[]{key};
			}
			if(opers.length > 3){
				throw new RuntimeException("参数格式不正确:" + key);
			}
			Class<?> claz = null;
			if(opers.length == 3){
				try {
					claz = Class.forName(opers[2]);
				} catch (ClassNotFoundException e) {
					throw new RuntimeException(e);
				}
			}
			Operator operator;
			if(opers.length == 1){
				operator = Operator.EQ;
			}else{
				operator = Operator.valueOf(opers[1]);
			}
			if(operator == Operator.NULL || operator == Operator.NOTNULL){
				value = operator;
			}
			if(value == null){
				continue;
			}
			String fields[];
			if(opers[0].indexOf(".") != -1){
				fields = opers[0].split(".");
			}else{
				fields = new String[]{opers[0]};
			}
			if(fields.length > 2){
				throw new RuntimeException("不支持多级查询");
			}
			if(fields.length == 1){
				String field = fields[0];
				Predicate predicate = getPredicate(root, cb, field, value, operator, claz);
				predicateList.add(predicate);
			}else{
				From<Entity, ?> join = joins.get(fields[0]);
				if(join == null){
					join = root.join(fields[0], JoinType.LEFT);
					joins.put(fields[0], join);
				}
				Predicate predicate = getPredicate(join, cb, fields[1], value, operator, claz);
				predicateList.add(predicate);
			}
		}
		List<Order> orders = new ArrayList<>();
		if(sort != null){
			Iterator<Sort.Order> sit = sort.iterator();
			while(sit.hasNext()){
				Sort.Order order = sit.next();
				Direction d = order.getDirection();
				String property[] = order.getProperty().split(":");
				Order o;
				Path<String> field;
				String fields[] = property[0].split(".");
				if(fields.length > 1){
					From<Entity, ?> join = joins.get(fields[0]);
					if(join == null){
						join = root.join(fields[0], JoinType.LEFT);
						joins.put(fields[0], join);
					}
					field = join.get(fields[1]);
				}else{
					field = root.get(property[0]);
				}
				if(property.length > 1){
					try {
						o = new OrderImpl(field.as(Class.forName(property[1])), d.isAscending());
					} catch (ClassNotFoundException e) {
						o = new OrderImpl(field, d.isAscending());
						e.printStackTrace();
					}
				}else{
					o = new OrderImpl(field, d.isAscending());
				}
				orders.add(o);
			}
		}
		if(!predicateList.isEmpty()){
			query.where(predicateList.toArray(new Predicate[predicateList.size()]));
		}
		if(!orders.isEmpty()){
			query.orderBy(orders);
		}
		return null;
	}

}
